#include <iostream>
#include <sstream>

#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/IdentifierTable.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Parse/ParseAST.h"
#include "clang/Rewrite/Rewriter.h"
#include "llvm/Config/config.h"
#include "llvm/Support/raw_ostream.h"

#include "LoopAnalyzer.h"
#include "LoopInfo.h"
#include "depSolve.h"

using namespace llvm;
using namespace clang;
using namespace std;

// the perennial C++ helper for printing ints
string itos(int n) {
    stringstream sst;
    sst << n;
    return sst.str();
}

class RewritingAnalyzer {

    std::string	_inFilename;
    // std::string	_outFilename;
    
    LangOptions		_langOpts;
    TargetOptions	_tarOpts;
    Diagnostic		*_diags;
    SourceManager	*_sm;
    FileSystemOptions _fileSystemOpts;
    FileManager		*_fm;
    TargetInfo		*_ti;
    Preprocessor	*_pp;
    // llvm::raw_fd_ostream	*_fos;
    HeaderSearch		*_headers;
    ASTContext		*_context;
    LoopAnalyzer		*_consumer;

    DiagnosticOptions _diagOpts;
    TextDiagnosticPrinter *_textDiagPrinter;
    llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> _diagIDs;
    
public:
    
    RewritingAnalyzer(const std::string &inFilename) :
        _inFilename(inFilename)/*, _outFilename(outFilename)*/ {
        
        _fm = new FileManager(_fileSystemOpts);
        _textDiagPrinter = new clang::TextDiagnosticPrinter(
                                                            llvm::outs(),
                                                            _diagOpts);
        _diags = new Diagnostic(_diagIDs, _textDiagPrinter);
        // _diags->setSuppressAllDiagnostics(true);	// FIXME diagnostics errors
        _sm = new SourceManager(*_diags, *_fm);
        _headers = new HeaderSearch(*_fm);
        
        // Setting the target machine properties
        _tarOpts.Triple = LLVM_HOSTTRIPLE; 
        _tarOpts.ABI = "";
        _tarOpts.CPU = "";
        _tarOpts.Features.clear();
        
        _ti = TargetInfo::CreateTargetInfo(*_diags, _tarOpts);
        _pp = new Preprocessor(*_diags, _langOpts, *_ti, *_sm, *_headers);
          
        _consumer = new LoopAnalyzer(_inFilename, *_diags, _langOpts);
    }
    
    virtual ~RewritingAnalyzer() {
        delete _pp;
        delete _ti;
        delete _sm;
        delete _diags;
        delete _context;
        delete _consumer;
        // delete _fos;
        delete _headers;
    }
    
    LoopNestInfo parse() {
        
        // Add input file
        const FileEntry* file = _fm->getFile(_inFilename);
        if (!file) {
            cerr << "Failed to open \'" << _inFilename << "\'" << endl;
            // return EXIT_FAILURE;
            exit(1);
        }
        _sm->createMainFileID(file);
        
        // do the source analysis
        IdentifierTable idTable(_langOpts);
        SelectorTable selTable;
        Builtin::Context builtinCtx(*_ti);
        _context = new ASTContext(_langOpts, *_sm, *_ti,
                                  idTable, selTable, builtinCtx, 0);
        _consumer->Initialize(*_context);
        // ParseAST will cause the consumer's methods to be run.
        ParseAST(*_pp, _consumer, *_context);
        
        return _consumer->loopInfo;
    }
};

void sendToTop(LoopNestInfo *loopInfo, int stmtIndex) {
  loopInfo->replaceStmt(0, stmtIndex);
  for(int i = 1; i <= stmtIndex; i++) {
    loopInfo->replaceStmt(i, i - 1);
  }
}

void alg(LoopNestInfo *loopInfo, DepGraph depGraph) {
  // walk in topological order (from the highest scc index)
  std::vector<std::vector<int> > scc = depGraph.getScc();
  for(int i = scc.size() - 2; i >= 0; i--) {
    if(scc[i].size() == 1) { // only one statement in the scc
      if(!depGraph.depCycle(scc[i][0])) {


	// send vector statement to the top and reorder the others accordingly
	sendToTop(loopInfo, scc[i][0]);
        loopInfo->insertStatementComment(0, "vector statement");
      } else {
	// generate sequential code (do nothing!)
      }
    } else { // multiple statements in the scc
	// generate sequential code (do nothing!)
    }

  }

}

int main() {
    // Initialize and run the analyzer
    RewritingAnalyzer analyzer("in.c");
    LoopNestInfo loopInfo = analyzer.parse();

    // my code begins ---------------------------
    DepGraph depGraph = generateDependencyGraph(loopInfo);

    depGraph.SCC();

    alg(&loopInfo, depGraph);
    // my code ends -----------------------------

    // ---- demonstration code - replace with your calls to the 
    //      dependency analysis code and generate vectorization comments
    
    // generateDependencyGraph(loopInfo); FOR EXAMPLE
    // loop statement reordering example: swap statements 1 and 2 (if they exist)
    /*    if (loopInfo.nStatements() >= 2) {
        loopInfo.replaceStmt(0, 1);
        loopInfo.replaceStmt(1, 0);

	// insert a comment before each statement in the for loop (in the new order.)
	for (int i = 0; i < loopInfo.nStatements(); i++) {
	  loopInfo.insertStatementComment(i, "statement " + itos(i));
	}
	}*/
    

    // -----------------------------------------------------------------
    
    // create output stream and write result
    string errors;
    raw_fd_ostream* outfile = new raw_fd_ostream("out.c", errors);
    loopInfo.writeSource(outfile);

}
